package com.zxy.ziems.server.energy.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zxy.ziems.server.classify.pojo.entity.ClassifyDeviceProperty;
import com.zxy.ziems.server.classify.service.ClassifyDevicePropertyService;
import com.zxy.ziems.server.constant.enums.CalculateTypeEnum;
import com.zxy.ziems.server.device.mapper.DeviceMapper;
import com.zxy.ziems.server.device.pojo.entity.DeviceEntity;
import com.zxy.ziems.server.domain.DeviceMeasureData;
import com.zxy.ziems.server.domain.DeviceMeasureDataIncrementHour;
import com.zxy.ziems.server.energy.mapper.DeviceMeasureDataIncrementHourMapper;
import com.zxy.ziems.server.energy.pojo.dto.DeviceMeasureDayCostDTO;
import com.zxy.ziems.server.energy.pojo.entity.DeviceMeasureDataDayEntity;
import com.zxy.ziems.server.energy.service.DataIncrementService;
import com.zxy.ziems.server.energy.service.DeviceMeasureDataIncrementHourService;
import com.zxy.ziems.server.energy.service.DeviceMeasureDataService;
import com.zxy.ziems.server.energy.service.EnergyPriceService;
import com.zxy.ziems.server.mybatis.core.service.ServiceImplX;
import com.zxy.ziems.server.sync.enums.SyncMetaType;
import com.zxy.ziems.server.sync.service.SyncMetaService;
import com.zxy.ziems.server.utils.RedissonLockUtil;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RLock;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import static com.zxy.ziems.server.constant.RedisKeyLockConstant.DEVICE_MEASURE_DATA_DAY_KEY;
import static com.zxy.ziems.server.constant.RedisKeyLockConstant.TMP_DEVICE_MEASURE_DATA_HOUR_KEY_;

/**
 * @ClassName: DataIncrementServiceImpl
 * @Author: yanzhiwei
 * @Description: 增量值处理
 * @Date: 2024/4/7 10:21
 */
@Service
@Slf4j
public class DataIncrementServiceImpl extends ServiceImplX<DeviceMeasureDataIncrementHourMapper, DeviceMeasureDataIncrementHour> implements DataIncrementService {
    @Autowired
    private DeviceMeasureDataIncrementHourService deviceMeasureDataIncrementHourService;
    @Autowired
    private DeviceMeasureDataService deviceMeasureDataService;
    @Autowired
    private ClassifyDevicePropertyService classifyDevicePropertyService;
    @Autowired
    private RedissonLockUtil redissonLockUtil;
    @Autowired
    private SyncMetaService syncMetaService;
    @Autowired
    private EnergyPriceService energyPriceService;

    // 实时值数据类型集合，来源于t_data_type表
    // 实时值每天数据处理时需要单独处理
    List<String> REAL_DATA_TYPE_ID_LIST =  Arrays.asList(
            // 压强
            "1790959492202430464",
            // 温度
            "1790959979890933760",
            // 频率
            "1790963801010929664",
            // 风力
            "1793230804971155457"
            );
    @Autowired
    private DeviceMapper deviceMapper;

    @Override
    public void handleIncrementData(Date startTime, Date endTime) {
        List<DeviceMeasureDataIncrementHour> deviceMeasureDataIncrementHourList = incrementData(startTime, endTime);
        deviceMeasureDataIncrementHourService.saveBatch(deviceMeasureDataIncrementHourList);
    }


    /**
     * 计算一段时间值
     *
     * @param startTime
     * @param endTime
     * @return
     */
    private List<DeviceMeasureDataIncrementHour> incrementData(Date startTime, Date endTime) {

        // 获取各个计算方式的设备id
        List<ClassifyDeviceProperty> classifyDevicePropertyList = classifyDevicePropertyService.list();
        List<String> avgIds = classifyDevicePropertyList.stream().filter(m -> m.getCalculateType() != null && m.getCalculateType().equals(CalculateTypeEnum.MEASURE.getCode())).map(m -> m.getDeviceId()).collect(Collectors.toList());
        List<String> addIds = classifyDevicePropertyList.stream().filter(m -> m.getCalculateType() != null && m.getCalculateType().equals(CalculateTypeEnum.INCREMENT.getCode())).map(m -> m.getDeviceId()).collect(Collectors.toList());

        List<DeviceMeasureDataIncrementHour> deviceMeasureDataIncrementHourAddList = add(startTime, endTime, addIds);
        List<DeviceMeasureDataIncrementHour> deviceMeasureDataIncrementHourAvgList = avg(startTime, endTime, avgIds);

        List<DeviceMeasureDataIncrementHour> deviceMeasureDataIncrementHourList = new ArrayList<>();
        deviceMeasureDataIncrementHourList.addAll(deviceMeasureDataIncrementHourAddList);
        deviceMeasureDataIncrementHourList.addAll(deviceMeasureDataIncrementHourAvgList);

        return deviceMeasureDataIncrementHourList;
    }

    /**
     * 增量值
     *
     * @return
     */
    private List<DeviceMeasureDataIncrementHour> add(Date startTime, Date endTime, List<String> ids) {
        if (ids.isEmpty()) {
            return new ArrayList<>();
        }

        Date firstTime = getBeforeHourDate(startTime);
        List<DeviceMeasureData> deviceMeasureDataFirstList = deviceMeasureDataService.list(firstTime, startTime, ids);
        List<DeviceMeasureData> deviceMeasureDataList = deviceMeasureDataService.list(startTime, endTime, ids);
        Map<String, List<DeviceMeasureData>> map1 = deviceMeasureDataFirstList.stream().collect(Collectors.groupingBy(m -> m.getDeviceId() + '_' + m.getDataTypeId()));
        Map<String, List<DeviceMeasureData>> map = deviceMeasureDataList.stream().collect(Collectors.groupingBy(m -> m.getDeviceId() + '_' + m.getDataTypeId()));

        List<DeviceMeasureDataIncrementHour> deviceMeasureDataIncrementHourList = new ArrayList<>();
        for (String s : map.keySet()) {
            List<DeviceMeasureData> deviceMeasureDataIncrementHourFirstList1 = map1.get(s);
            List<DeviceMeasureData> deviceMeasureDataIncrementHourList1 = map.get(s);
            List<DeviceMeasureData> deviceMeasureDataIncrementHourFirstList2 = deviceMeasureDataIncrementHourFirstList1.stream().sorted(Comparator.comparing(DeviceMeasureData::getMeasureTime)).collect(Collectors.toList());
            List<DeviceMeasureData> deviceMeasureDataIncrementHourList2 = deviceMeasureDataIncrementHourList1.stream().sorted(Comparator.comparing(DeviceMeasureData::getMeasureTime)).collect(Collectors.toList());

            BigDecimal incrementValue = new BigDecimal(0);
            if (!deviceMeasureDataIncrementHourList2.isEmpty()) {
                DeviceMeasureData deviceMeasureDataIncrementHour1 = deviceMeasureDataIncrementHourFirstList2.get(deviceMeasureDataIncrementHourFirstList2.size() - 1);
                DeviceMeasureData deviceMeasureDataIncrementHour2 = deviceMeasureDataIncrementHourList2.get(deviceMeasureDataIncrementHourList2.size() - 1);
                incrementValue = deviceMeasureDataIncrementHour2.getMeasureValue().subtract(deviceMeasureDataIncrementHour1.getMeasureValue());

                DeviceMeasureDataIncrementHour deviceMeasureDataIncrementHour = new DeviceMeasureDataIncrementHour();
                BeanUtils.copyProperties(deviceMeasureDataIncrementHourList1.get(0), deviceMeasureDataIncrementHour);
                deviceMeasureDataIncrementHour.setIncrementValue(incrementValue);
                deviceMeasureDataIncrementHourList.add(deviceMeasureDataIncrementHour);
            }
        }

        return deviceMeasureDataIncrementHourList;
    }

    /**
     * 平均值
     *
     * @return
     */
    private List<DeviceMeasureDataIncrementHour> avg(Date startTime, Date endTime, List<String> ids) {
        if (ids.isEmpty()) {
            return new ArrayList<>();
        }
        List<DeviceMeasureData> deviceMeasureDataList = deviceMeasureDataService.list(startTime, endTime, ids);
        Map<String, List<DeviceMeasureData>> map = deviceMeasureDataList.stream().collect(Collectors.groupingBy(m -> m.getDeviceId() + '_' + m.getDataTypeId()));

        List<DeviceMeasureDataIncrementHour> deviceMeasureDataIncrementHourList = new ArrayList<>();
        for (String s : map.keySet()) {
            List<DeviceMeasureData> deviceMeasureDataIncrementHourList1 = map.get(s);


            BigDecimal incrementValue = new BigDecimal(0);
            if (!deviceMeasureDataIncrementHourList1.isEmpty()) {
                BigDecimal total = deviceMeasureDataIncrementHourList1.stream()
                        .map(DeviceMeasureData::getMeasureValue)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);
                incrementValue = total.divide(new BigDecimal(deviceMeasureDataIncrementHourList1.size()));

                DeviceMeasureDataIncrementHour deviceMeasureDataIncrementHour = new DeviceMeasureDataIncrementHour();
                BeanUtils.copyProperties(deviceMeasureDataIncrementHourList1.get(0), deviceMeasureDataIncrementHour);
                deviceMeasureDataIncrementHour.setIncrementValue(incrementValue);
                deviceMeasureDataIncrementHourList.add(deviceMeasureDataIncrementHour);
            }
        }
        return deviceMeasureDataIncrementHourList;
    }

    /**
     * 获取当前时间前一个小时（具体到小时）
     *
     * @return
     */
    private static Date getBeforeHourDate(Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        calendar.set(Calendar.HOUR, 0);

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:00:00");
        return calendar.getTime();
    }

    @Transactional
    @Override
    public LocalDate syncDeviceMeasureDataDay(LocalDate date) {
        String key = DEVICE_MEASURE_DATA_DAY_KEY;

        RLock lock = redissonLockUtil.lock(key);
        lock.lock();

        try {
            log.info("lock==date={}", date);
            LocalDate res = date.plusDays(1);
            // 判断当前天有没有数据
//            List<DeviceMeasureDataDayEntity> dayDataByToday = deviceMeasureDataService.getDayDataByDate(date);
//            Set<String> deviceIdTodaySet = new HashSet<>();
//            if (!dayDataByToday.isEmpty()) {
//                deviceIdTodaySet = dayDataByToday.stream().map(DeviceMeasureDataDayEntity::getDeviceId).collect(Collectors.toSet());
//            }


            // 获取前天数据，定时任务执行已经过了0点
            List<DeviceMeasureDataDayEntity> dayData = deviceMeasureDataService.getDayDataByDate(date.plusDays(-1));

            //查出手动维护数据,不处理
            QueryWrapper<DeviceEntity> deviceSgwh = new QueryWrapper<>();
            deviceSgwh.lambda().eq(DeviceEntity::getTypeCode,"sgwh");
            List<DeviceEntity> deviceEntityList = deviceMapper.selectList(deviceSgwh);
            Set<String> deviceIdSet = deviceEntityList.stream().map(DeviceEntity::getId).collect(Collectors.toSet());

            // 过滤掉手动维护的数据
            dayData.removeIf(m -> deviceIdSet.contains(m.getDeviceId()));

            // 获取通过天数据统计的费用
            List<DeviceMeasureDayCostDTO> dayPrice = deviceMeasureDataIncrementHourService.getDayPrice(date);
            Map<String, BigDecimal> deviceCostMap = dayPrice.stream().collect(Collectors.toMap(m -> m.getDeviceId() + "-" + m.getDataTypeId(), DeviceMeasureDayCostDTO::getCost));

            // 获取当前数据，用24点数据代表前一天的数据
            List<DeviceMeasureData> hisData = deviceMeasureDataService.list();
//            List<DeviceMeasureDataDayDTO> hisData = deviceMeasureDataHistoryMapper.getHistoryDayStatistics(DateUtils.getStartTime(date), DateUtils.getEndTime(date));

            List<DeviceMeasureDataDayEntity> insertData = new ArrayList<>();
            if (CollectionUtil.isEmpty(dayData)) { // 第一次同步
                for (DeviceMeasureData hisDatum : hisData) {
                    DeviceMeasureDataDayEntity entity = setDeviceMeasureDataDayEntity(date, hisDatum);
                    setDayPrice(entity, deviceCostMap);
                    insertData.add(entity);
                }
            } else {
                Map<String, DeviceMeasureDataDayEntity> dayDeviceData = dayData.stream().collect(Collectors.toMap(k -> k.getDeviceId() + "-" + k.getDataTypeId(), v -> v));
                Map<String, DeviceMeasureData> deviceData = hisData.stream().collect(Collectors.toMap(k -> k.getDeviceId() + "-" + k.getDataTypeId(), v -> v));

                // 从昨天数据中获取与今天数据的比对，如果没有则给今天值设置为0（此种情况为设备异常了）
                dayDeviceData.forEach((k, v) -> {
                    DeviceMeasureData dataDayDTO = deviceData.get(k);
                    DeviceMeasureDataDayEntity entity = new DeviceMeasureDataDayEntity();
                    entity.setDeviceId(v.getDeviceId());
                    entity.setDataTypeId(v.getDataTypeId());
                    entity.setUnitId(v.getUnitId());
                    // 当采集的是实时值，温度，并且没有当天采集的数据时设置为0
                    if(REAL_DATA_TYPE_ID_LIST.contains(v.getDataTypeId())){
                        // 是当天采集的数据
                        if(dataDayDTO == null || !dataDayDTO.getMeasureTime().toLocalDate().isEqual(date.plusDays(1))){
                            entity.setMeasureValue(BigDecimal.ZERO);
                        }else{
                            entity.setMeasureValue(dataDayDTO.getMeasureValue());
                        }
                    }else{
                        entity.setMeasureValue(dataDayDTO == null ? BigDecimal.ZERO : dataDayDTO.getMeasureValue());
                    }
                    entity.setIncrementValue(dataDayDTO == null ? BigDecimal.ZERO.subtract(v.getMeasureValue()) : dataDayDTO.getMeasureValue().subtract(v.getMeasureValue()));
//                    entity.setAvgValue(dataDayDTO == null ? BigDecimal.ZERO : dataDayDTO.getAvg());
                    entity.setMeasureTime(date);
                    entity.setCreateTime(LocalDateTime.now());
                    setDayPrice(entity, deviceCostMap);
                    insertData.add(entity);
                });

                // 新增的指标值
                List<Map.Entry<String, DeviceMeasureData>> newDeviceList = deviceData.entrySet().stream()
                        .filter(m -> !dayDeviceData.keySet().contains(m.getKey())).collect(Collectors.toList());
                if (CollectionUtil.isNotEmpty(newDeviceList)) {
                    for (Map.Entry<String, DeviceMeasureData> entry : newDeviceList) {
                        DeviceMeasureData hisDatum = entry.getValue();
                        DeviceMeasureDataDayEntity entity = setDeviceMeasureDataDayEntity(date, hisDatum);
                        setDayPrice(entity, deviceCostMap);
                        insertData.add(entity);
                    }
                }

            }

            if (CollectionUtil.isNotEmpty(insertData)) {
                deviceMeasureDataService.batchSave(insertData);
            }

            syncMetaService.saveSyncTime(date.atStartOfDay(), SyncMetaType.DEVICE_MEASURE_DATA_DAY);
            return res;
        } catch (Exception e) {
            log.error("syncDeviceMeasureDataDay==date={} err=", date, e);
            throw new RuntimeException(e);
        } finally {
            log.info("unlock==date={}", date);
            redissonLockUtil.unlock(key);
        }
    }

    private void setDayPrice(DeviceMeasureDataDayEntity entity, Map<String, BigDecimal> deviceCostMap) {
        entity.setIncrementCost(deviceCostMap.get(entity.getDeviceId() + "-" + entity.getDataTypeId()));
    }


    @Transactional
    @Override
    public LocalDateTime syncDeviceMeasureDataHour(LocalDateTime date) {
        String key = TMP_DEVICE_MEASURE_DATA_HOUR_KEY_;

        RLock lock = redissonLockUtil.lock(key);
        lock.lock();

        try {
            log.info("lock==date={}", date);
            LocalDateTime res = date.plusHours(1);

            Long count = deviceMeasureDataService.countDayHourDataByDate(date);
            if (count > 0) {
                syncMetaService.saveSyncTime(date, SyncMetaType.DEVICE_MEASURE_DATA_INCREMENT_HOUR);
                return res;
            }
            // 获取前小时数据
            List<DeviceMeasureDataIncrementHour> dayData = deviceMeasureDataService.getDayHourDataByDate(date.plusHours(-1));

            // 获取当前数据
            List<DeviceMeasureData> hisData = deviceMeasureDataService.list();

            List<DeviceMeasureDataIncrementHour> insertData = new ArrayList<>();
            if (CollectionUtil.isEmpty(dayData)) { // 第一次同步
                for (DeviceMeasureData hisDatum : hisData) {
                    DeviceMeasureDataIncrementHour entity = setDeviceMeasureDataHourEntity(date, hisDatum);
                    insertData.add(entity);
                }
            } else {
                Map<String, DeviceMeasureDataIncrementHour> dayDeviceData = dayData.stream().collect(Collectors.toMap(k -> k.getDeviceId() + "-" + k.getDataTypeId(), v -> v));
                Map<String, DeviceMeasureData> deviceData = hisData.stream().collect(Collectors.toMap(k -> k.getDeviceId() + "-" + k.getDataTypeId(), v -> v));

                // 从上一小时数据中获取与当前小时数据的比对，如果没有则给当前小时值设置为0（此种情况为设备异常了）
                dayDeviceData.forEach((k, v) -> {
                    DeviceMeasureData dataDayDTO = deviceData.get(k);
                    DeviceMeasureDataIncrementHour entity = new DeviceMeasureDataIncrementHour();
                    entity.setDeviceId(v.getDeviceId());
                    entity.setDataTypeId(v.getDataTypeId());
                    entity.setUnitId(v.getUnitId());
                    // 当采集的是实时值，温度，并且没有当前小时采集的数据时设置为0
                    if(REAL_DATA_TYPE_ID_LIST.contains(v.getDataTypeId())){
                        if(dataDayDTO == null || !dataDayDTO.getMeasureTime().toLocalDate().isEqual(LocalDate.now())){
                            entity.setMeasureValue(BigDecimal.ZERO);
                        }else{
                            entity.setMeasureValue(dataDayDTO.getMeasureValue());
                        }
                    }else{
                        entity.setMeasureValue(dataDayDTO == null ? BigDecimal.ZERO : dataDayDTO.getMeasureValue());
                    }
                    BigDecimal incrementValue = dataDayDTO == null ? BigDecimal.ZERO.subtract(v.getMeasureValue()) : dataDayDTO.getMeasureValue().subtract(v.getMeasureValue());
                    entity.setIncrementValue(incrementValue);
                    setHourPrice(date, v.getDataTypeId(), entity);
                    entity.setMeasureTime(date);
                    entity.setCreateTime(LocalDateTime.now());
                    insertData.add(entity);
                });

                // 新增的指标值
                List<Map.Entry<String, DeviceMeasureData>> newDeviceList = deviceData.entrySet().stream()
                        .filter(m -> !dayDeviceData.keySet().contains(m.getKey())).collect(Collectors.toList());
                if (CollectionUtil.isNotEmpty(newDeviceList)) {
                    for (Map.Entry<String, DeviceMeasureData> entry : newDeviceList) {
                        DeviceMeasureData hisDatum = entry.getValue();
                        DeviceMeasureDataIncrementHour entity = setDeviceMeasureDataHourEntity(date, hisDatum);
                        setHourPrice(date, entity.getDataTypeId(), entity);
                        insertData.add(entity);
                    }
                }

            }

            if (CollectionUtil.isNotEmpty(insertData)) {
                deviceMeasureDataIncrementHourService.saveBatch(insertData);
            }

            syncMetaService.saveSyncTime(date, SyncMetaType.DEVICE_MEASURE_DATA_INCREMENT_HOUR);
            return res;
        } catch (Exception e) {
            log.error("syncDeviceMeasureDataHour==dateTime={} err=", date, e);
            throw new RuntimeException(e);
        } finally {
            log.info("unlock==dateTime={}", date);
            redissonLockUtil.unlock(key);
        }
    }

    /**
     * 设置小时值的费用，根据dataType值进行设置费用
     * @param date
     * @param dataTypeId
     * @param entity
     */
    private void setHourPrice(LocalDateTime date, String dataTypeId, DeviceMeasureDataIncrementHour entity) {
        BigDecimal incrementValue = entity.getIncrementValue();
        BigDecimal price = energyPriceService.getEnergyPriceByDataTypeId(dataTypeId, date);
        if (price != null) {
            entity.setIncrementCost(incrementValue.multiply(price));
        }
    }

    private DeviceMeasureDataIncrementHour setDeviceMeasureDataHourEntity(LocalDateTime date, DeviceMeasureData hisDatum) {
        DeviceMeasureDataIncrementHour entity = new DeviceMeasureDataIncrementHour();
        entity.setDeviceId(hisDatum.getDeviceId());
        entity.setDataTypeId(hisDatum.getDataTypeId());
        entity.setMeasureValue(hisDatum.getMeasureValue());
        entity.setIncrementValue(hisDatum.getMeasureValue());
        entity.setUnitId(hisDatum.getUnitId());
        entity.setMeasureTime(date);
        entity.setCreateTime(LocalDateTime.now());
        return entity;

    }

    @NotNull
    private static DeviceMeasureDataDayEntity setDeviceMeasureDataDayEntity(LocalDate date, DeviceMeasureData hisDatum) {
        DeviceMeasureDataDayEntity entity = new DeviceMeasureDataDayEntity();
        entity.setDeviceId(hisDatum.getDeviceId());
        entity.setDataTypeId(hisDatum.getDataTypeId());
        entity.setMeasureValue(hisDatum.getMeasureValue());
        entity.setIncrementValue(hisDatum.getMeasureValue());
        entity.setUnitId(hisDatum.getUnitId());
        entity.setMeasureTime(date);
        entity.setCreateTime(LocalDateTime.now());
        return entity;
    }
}
